Skip to content

Conversation

devongovett
Copy link
Member

@devongovett devongovett commented Jun 8, 2024

Closes #5067

This PR implements a Virtualizer in React Aria Components, using the collection renderer system added in #5912.

There are 3 parts which you can review commit by commit:

  1. Add a new LayoutDelegate interface, which enables us to share a common KeyboardDelegate implementation between virtualized and non-virtualized collections. Previously, ListLayout implemented the KeyboardDelegate interface itself, which was mostly duplicated with the DOM-based ListKeyboardDelegate. It also resulted in us needing to sync data into the layout such as disabledKeys. Now we can use ListKeyboardDelegate in both cases. It accepts a LayoutDelegate, which provides the rectangles of item elements and the collection itself. There is a default DOMLayoutDelegate implementation, and in addition, all Layout classes automatically implement this interface as well.
  2. Unify the Spectrum and Aria column resizing implementations. We now use the hook in Spectrum and pass the resulting columnWidths into the virtualizer layout via layoutOptions, rather than storing state in the layout. I've also managed to simplify the algorithm for resizing a column. This enables RAC to implement resizing the same way as RSP.
  3. Add Virtualizer to React Aria Components. This is implemented as a CollectionRenderer, which has been refactored a bit. It now provides two components: CollectionRoot renders the root items of a collection, and CollectionBranch renders the children of a branch item. Virtualizer attaches scroll events to the provided scrollRef at the root, determines which views need to be displayed, and renders those.

The end result is that you can wrap any collection component in a Virtualizer, pass in a layout as a prop, and the component will have virtualized scrolling. The rest of the API is the same as it is today.

let layout = useMemo(() => {
  return new ListLayout({
    rowHeight: 25
  });
}, []);

return (
  <Virtualizer layout={layout}>
    <GridList style={{width: 200, height: 400}} items={items}>
      {item => <GridListItem>{item.name}</GridListItem>}
    </GridList>
  </Virtualizer>
);

This means the layouts are also customizable. For example, you could pass a GridLayout here and the items would be arranged in a grid. Virtualizer delegates to the layout for keyboard navigation and drag and drop automatically, and persists the focused key in the DOM. Individual components may also add additional ARIA attributes when virtualized, e.g. aria-setsize and aria-posinset. Otherwise, they are unaware of virtualization, which means you only pay the bundle size cost when needed, and you could even implement a custom CollectionRenderer to use a different virtualization library if you wanted.

This enables us to share a common KeyboardDelegate implementation between virtualized and non-virtualized collections
We now use the hook in Spectrum and pass the resulting columnWidths into the virtualizer layout
export interface AriaTableProps extends GridProps {
/** The layout object for the table. Computes what content is visible and how to position and style them. */
layout?: Layout<Node<T>>
layoutDelegate?: LayoutDelegate
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically a breaking change but I'm hoping we can get away with it since virtualizer wasn't previously public?

Copy link
Member

@snowystinger snowystinger Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i suspect it'll make some people unhappy anyways, but I think we'll get back into good graces by adding all the virtualizer/layoutDelegate support

otherwise, how impossible is it to support both?

or they could pass in their own copy of the old TableLayout? I guess the issue is the keyboard delegate stuff?

}

if (nodes.length === 0) {
if (nodes.length === 0 && this.enableEmptyState) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In RAC, the loading and empty states are handled in the component rather than in the layout. Ideally this would also the the case in RSP, but I haven't found a good way to implement that yet.

columnLayout: TableColumnLayout<T>,
initialCollection: TableCollection<T>
export interface TableLayoutOptions<T> extends ListLayoutOptions<T> {
scrollContainer?: 'table' | 'body'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to handle a difference between RSP and RAC. In RSP, only the TableBody scrolls, whereas (so far) in RAC, the whole table scrolls and the column headers have position: sticky. This affects the positioning of the body rows, which are relative to the body in RSP, and relative to the whole table in RAC.

}

buildChild(node: Node<T>, x: number, y: number): LayoutNode {
protected buildChild(node: Node<T>, x: number, y: number, parentKey: Key | null): LayoutNode {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now takes a parentKey as a parameter rather than using the node's parentKey in order to properly handled TreeView's flattened collection. We want the parent LayoutInfo here, not the parent Node.

return this.getItem(keys[idx]);
}

getChildren(key: Key): Iterable<GridNode<T>> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interoperability with RAC TableCollection.

newWidths.set(key, width);
});

return newWidths;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I am missing something here, but it seemed like we could simplify this algorithm a lot. We just need to lock in all of the columns to the left of the resizing one to their previous pixel values, and set the width of the resizing column. I don't think we need to run the sizing algorithm here at all, we can do that after the next render when the new widths are set. All the tests passed and it seemed to behave ok.

Copy link
Member

@snowystinger snowystinger Jun 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at one point i think we needed to run the layout here so that the internal values for the columns was up to date

I think the changes you made that no longer needed since it'll actually update in render

// React calling removeChild on every item in the collection.
document.isMounted = false;
};
}, [document]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improves performance a lot when unmounting a collection component with many items (e.g. 10,000 rows). Noticed when switching stories.

@rspbot
Copy link

rspbot commented Jun 8, 2024

@rspbot
Copy link

rspbot commented Jun 11, 2024

reidbarber
reidbarber previously approved these changes Jun 14, 2024
import {TableColumnResizeStateContext} from './Table';
import {useContext, useMemo} from 'react';

export class TableLayout<T> extends BaseTableLayout<T> implements LayoutOptionsDelegate<TableLayoutProps> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is using the BaseTableLayout, I found that it doesn't handle the "loader" type node that the load more spinner collection element due to

protected buildNode(node: GridNode<T>, x: number, y: number): LayoutNode {
switch (node.type) {
case 'headerrow':
return this.buildHeaderRow(node, x, y);
case 'item':
return this.buildRow(node, x, y);
case 'column':
case 'placeholder':
return this.buildColumn(node, x, y);
case 'cell':
return this.buildCell(node, x, y);
default:
throw new Error('Unknown node type ' + node.type);
not understanding what that node type is. Bit torn on whether or not the TableLayout should be updated to handle "loader" type nodes or if the leaf component for the loader should be changed to "item" (this would pose some difficulties in differentiating the loaders from normal rows but can probably be handled collection side some how)

Or would the expectation be that the TableLayout be replaced/extended to handle new node types? Thinking from an angle where perhaps a section header/loader would need its calculated row height differentiated from a typical row

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they were all items, how would we differentiate them elsewhere? Do we need to?

Otherwise I think it's fine to add more item types to the layout, would just work the same as item. I don't expect we'd be adding them that often.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I've made the loader node trigger this.buildRow as well. I do still wonder if there should be additional customization allowed for setting the height of the loader row independent of the row height but that can be handled in the future if requested.

…alizer

# Conflicts:
#	packages/react-aria-components/test/GridList.test.js
@rspbot
Copy link

rspbot commented Jun 15, 2024

function ResizableTableContainer(props: ResizableTableContainerProps, ref: ForwardedRef<HTMLDivElement>) {
let objectRef = useObjectRef(ref);
let containerRef = useObjectRef(ref);
let tableRef = useRef<HTMLTableElement>(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let tableRef = useRef<HTMLTableElement>(null);
let tableRef = useRef<HTMLTableElement | null>(null);

@snowystinger
Copy link
Member

snowystinger commented Jun 15, 2024

Comment on lines 260 to 262
// Add some margin around the loader to ensure that scrollbars don't flicker in and out.
let rect = new Rect(40, Math.max(y, 40), (width || this.virtualizer.visibleRect.width) - 80, children.length === 0 ? this.virtualizer.visibleRect.height - 80 : 60);
let loader = new LayoutInfo('loader', 'loader', rect);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just wanted to point out that this isLoading block doesn't trigger for RAC tables since RAC TableBody doesn't take in loadingState (which is good and expected) but if the user coincidentally passes loadingState='loading' then this does trigger and messes up the layout by creating the loader layout info that we only actually use in RSP TableView.

I don't think this is really something to worry about probably but just wanted to call it out since this happens since we access the props set on the collection nodes in several places

import {TableColumnResizeStateContext} from './Table';
import {useContext, useMemo} from 'react';

export class TableLayout<T> extends BaseTableLayout<T> implements LayoutOptionsDelegate<TableLayoutProps> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing I just realized is that this exposes options like loaderHeight that actually don't do anything due to how TableLayout implements its own buildCollection. I've gotten rid of it from stately's TableLayout but I'll double check if there are any other options that need to be removed/aren't applicable to RAC TableLayout

@rspbot
Copy link

rspbot commented Jun 17, 2024

RAC TableCollection now lists the head and body as its root nodes rather than the rows.
@rspbot
Copy link

rspbot commented Jun 18, 2024

LFDanLu
LFDanLu previously approved these changes Jun 18, 2024
Copy link
Member

@LFDanLu LFDanLu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified that drag and drop works now and that behavior for RAC/RSP virtualized components work as expected. Only a couple of small questions but not blockers IMO

this.isLoading = invalidationContext.layoutOptions.isLoading;
this.direction = invalidationContext.layoutOptions.direction;
this.isLoading = invalidationContext.layoutOptions?.isLoading || false;
this.direction = invalidationContext.layoutOptions?.direction || 'rtl';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this default rtl? May not matter since the expectation is that the layoutOptions passed to Virtualizer/whatever provides the invalidationContext has the proper direction but just found it interesting that rtl was the default if not provided

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo, good catch


rect.width = this.layoutInfos.get('header').rect.width;
rect.width = this.layoutInfos.get(this.collection.head?.key ?? 'header').rect.width;
rect.height = height + 1; // +1 for bottom border
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that our layouts have some extra calculations built in like the above that adds 1 to the row height for the border. Will this be a bit strange for a RAC user who provides something like rowHeight: 25 to their TableLayout but then gets an actual row height of 26px?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm yeah. I am wondering if we split out a spectrum-specific layout from the generic one...

Copy link
Member

@LFDanLu LFDanLu Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it would ideal to split them. I'll add to the canvas to discuss this further Nevermind its already there, I'll leave some of my thoughts

@rspbot
Copy link

rspbot commented Jun 18, 2024

snowystinger
snowystinger previously approved these changes Jun 19, 2024
Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, happy to get in and we can follow up on the other ideas we had. Would be good if we can split any of that work amongst us

@rostero1
Copy link

I'm testing out this branch and if I have dynamic heights for each element and scroll to the bottom it does not show all the elements and there is a large white gap at the bottom resulting in a blank viewport. I apologies if dynamic height isn't supported.

This is for the VirtualizedListBox story, where I moved sections out of the component.

function generateRandomString(minLength: number, maxLength: number): string {
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  const length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;

  let result = '';
  for (let i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * characters.length));
  }

  return result;
}

let sections: {id: string, name: string, children: {id: string, name: string}[]}[] = [];
for (let s = 0; s < 10; s++) {
  let items: {id: string, name: string}[] = [];
  for (let i = 0; i < 100; i++) {
    const l = (s * 5) + i + 10;
    items.push({id: `item_${s}_${i}`, name: `Item ${i} ${generateRandomString(l, l)}`});
  }
  sections.push({id: `section_${s}`, name: `Section ${s}`, children: items});
}

Also, will these virtualizer handle the case where an element could resize at any point? Such, as a row getting more data to render?

@devongovett
Copy link
Member Author

It should work if you specify an estimatedRowHeight rather than rowHeight on the ListLayout.

@rostero1
Copy link

It should work if you specify an estimatedRowHeight rather than rowHeight on the ListLayout.

I forgot to mention that I also changed rowHeight to estimatedRowHeight

@devongovett
Copy link
Member Author

Hmm your example is working for me locally... Any other steps to reproduce what you're seeing?

@rostero1
Copy link

Thanks for checking. The only other difference is I have style={{ wordBreak: 'break-word'}} added to <MyListBoxItem .

Sorry for not creating a codebandbox for this.

LFDanLu
LFDanLu previously approved these changes Jun 19, 2024
Copy link
Member

@LFDanLu LFDanLu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy for this to go in as is, we can discuss breaking out the RSP specific layout logic separately

…alizer

# Conflicts:
#	packages/@react-aria/dnd/src/ListDropTargetDelegate.ts
#	packages/@react-aria/grid/src/GridKeyboardDelegate.ts
#	packages/@react-aria/table/src/useTable.ts
#	packages/@react-aria/virtualizer/src/ScrollView.tsx
#	packages/@react-spectrum/table/src/Resizer.tsx
#	packages/react-aria-components/src/Table.tsx
#	packages/react-aria-components/stories/GridList.stories.tsx
#	packages/react-aria-components/test/GridList.test.js
@devongovett devongovett dismissed stale reviews from LFDanLu and snowystinger via 620c7c5 June 20, 2024 02:32
@devongovett
Copy link
Member Author

@rostero1 thanks, I see what you mean. I will look at it in a followup PR.

@rspbot
Copy link

rspbot commented Jun 20, 2024

@rspbot
Copy link

rspbot commented Jun 20, 2024

## API Changes

unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any', access: 'private' }
unknown top level export { type: 'any', access: 'private' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'identifier', name: 'Column' }
unknown top level export { type: 'identifier', name: 'Column' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
undefined already in set

@react-aria/autocomplete

AriaSearchAutocompleteOptions

 AriaSearchAutocompleteOptions<T> {
   inputRef: RefObject<HTMLInputElement | null>
   keyboardDelegate?: KeyboardDelegate
+  layoutDelegate?: LayoutDelegate
   listBoxRef: RefObject<HTMLElement | null>
   popoverRef: RefObject<HTMLDivElement | null>
 }

it changed:

  • useSearchAutocomplete

@react-aria/combobox

AriaComboBoxOptions

 AriaComboBoxOptions<T> {
   buttonRef?: RefObject<Element | null>
   inputRef: RefObject<HTMLInputElement | null>
   keyboardDelegate?: KeyboardDelegate
+  layoutDelegate?: LayoutDelegate
   listBoxRef: RefObject<HTMLElement | null>
   popoverRef: RefObject<Element | null>
 }

it changed:

  • useComboBox

@react-aria/dnd

DragPreview

 ListDropTargetDelegate {
-  constructor: (Collection<Node<unknown>>, RefObject<HTMLElement | null>, ListDropTargetDelegateOptions) => void
+  constructor: (Iterable<Node<unknown>>, RefObject<HTMLElement | null>, ListDropTargetDelegateOptions) => void
   getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
 }

@react-aria/grid

GridKeyboardDelegate

changed by:

  • GridKeyboardDelegateOptions
 GridKeyboardDelegate<C extends GridCollection<T>, T> {
   collection: GridCollection<T>
-  constructor: (GridKeyboardDelegateOptions<T, GridCollection<T>>) => void
+  constructor: (GridKeyboardDelegateOptions<GridCollection<T>>) => void
   getFirstKey: (Key, boolean) => void
   getKeyAbove: (Key) => void
   getKeyBelow: (Key) => void
   getKeyForSearch: (string, Key) => void
   getKeyPageAbove: (Key) => void
   getKeyPageBelow: (Key) => void
   getKeyRightOf: (Key) => void
   getLastKey: (Key, boolean) => void
 }
 

GridKeyboardDelegateOptions

-GridKeyboardDelegateOptions<C, T> {
-  collator?: Intl.Collator
-  collection: C
-  direction: Direction
-  disabledBehavior?: DisabledBehavior
-  disabledKeys: Set<Key>
-  focusMode?: 'row' | 'cell'
-  layout?: Layout<Node<T>>
-  ref?: RefObject<HTMLElement | null>
-}
+

it changed:

  • GridKeyboardDelegate

undefined

-
+GridKeyboardDelegateOptions<C> {
+  collator?: Intl.Collator
+  collection: C
+  direction: Direction
+  disabledBehavior?: DisabledBehavior
+  disabledKeys: Set<Key>
+  focusMode?: 'row' | 'cell'
+  layoutDelegate?: LayoutDelegate
+  ref?: RefObject<HTMLElement | null>
+}

@react-aria/gridlist

AriaGridListOptions

 AriaGridListOptions<T> {
   disabledBehavior?: DisabledBehavior
   isVirtualized?: boolean
   keyboardDelegate?: KeyboardDelegate
   keyboardNavigationBehavior?: 'arrow' | 'tab' = 'arrow'
+  layoutDelegate?: LayoutDelegate
   linkBehavior?: 'action' | 'selection' | 'override' = 'action'
   onAction?: (Key) => void
   shouldFocusWrap?: boolean = false
 }

it changed:

  • useGridList

@react-aria/listbox

AriaListBoxProps

 AriaListBoxOptions<T> {
   isVirtualized?: boolean
   keyboardDelegate?: KeyboardDelegate
+  layoutDelegate?: LayoutDelegate
   linkBehavior?: 'action' | 'selection' | 'override' = 'override'
   shouldFocusOnHover?: boolean
   shouldSelectOnPressUp?: boolean
   shouldUseVirtualFocus?: boolean
 

@react-aria/selection

AriaSelectableListOptions

 AriaSelectableListOptions {
   collection: Collection<Node<unknown>>
   disabledKeys: Set<Key>
   keyboardDelegate?: KeyboardDelegate
+  layoutDelegate?: LayoutDelegate
 }

it changed:

  • useSelectableList

DOMLayoutDelegate

-
+DOMLayoutDelegate {
+  constructor: (RefObject<HTMLElement>) => void
+  getContentSize: () => Size
+  getItemRect: (Key) => Rect | null
+  getVisibleRect: () => Rect
+}

@react-aria/table

useTable

changed by:

  • AriaTableProps
 useTable<T> {
-  props: AriaTableProps<T>
+  props: AriaTableProps
   state: TableState<T> | TreeGridState<T>
   ref: RefObject<HTMLElement | null>
   returnVal: undefined
 }

AriaTableProps

-AriaTableProps<T> {
-  layout?: Layout<Node<T>>
-}
+

it changed:

  • useTable

undefined

-
+AriaTableProps {
+  layoutDelegate?: LayoutDelegate
+}

@react-aria/utils

mergeRefs

 mergeRefs<T> {
-  refs: Array<ForwardedRef<T> | MutableRefObject<T>>
+  refs: Array<ForwardedRef<T> | MutableRefObject<T> | null | undefined>
   returnVal: undefined
 }

@react-aria/virtualizer

useVirtualizerItem

 VirtualizerItem {
   children: ReactNode
   className?: string
-  parent?: LayoutInfo
+  parent?: LayoutInfo | null
 }

layoutInfoToStyle

 ScrollView {
-  children: ReactNode
+  children?: ReactNode
   contentSize: Size
   innerStyle?: CSSProperties
   onScrollEnd?: () => void
   onScrollStart?: () => void
   scrollDirection?: 'horizontal' | 'vertical' | 'both'
   sizeToFit?: 'width' | 'height'
 }
 

setScrollLeft

-
+useScrollView {
+  props: ScrollViewProps
+  ref: RefObject<HTMLElement | null>
+  returnVal: undefined
+}

@react-spectrum/listbox

ListBox

 useListBoxLayout<T> {
-  state: ListState<T>
   returnVal: undefined
 }

@react-stately/layout

ListLayout

changed by:

  • ListLayoutProps
 ListLayout<T> {
-  allowDisabledKeyFocus: boolean
-  buildChild: (Node<T>, number, number) => LayoutNode
-  buildCollection: () => Array<LayoutNode>
-  buildHeader: (Node<T>, number, number) => LayoutNode
-  buildItem: (Node<T>, number, number) => LayoutNode
-  buildNode: (Node<T>, number, number) => LayoutNode
-  buildSection: (Node<T>, number, number) => LayoutNode
-  collection: Collection<Node<T>>
   constructor: (ListLayoutOptions<T>) => void
-  disabledKeys: Set<Key>
-  ensureLayoutInfo: (Key) => void
   getContentSize: () => void
   getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
-  getFirstKey: () => Key | null
-  getKeyAbove: (Key) => Key | null
-  getKeyBelow: (Key) => Key | null
-  getKeyForSearch: (string, Key) => Key | null
-  getKeyPageAbove: (Key) => Key | null
-  getKeyPageBelow: (Key) => Key | null
-  getLastKey: () => Key | null
   getLayoutInfo: (Key) => void
   getVisibleLayoutInfos: (Rect) => void
-  isLoading: boolean
-  isValid: (Node<T>, number) => void
-  isVisible: (LayoutNode, Rect) => void
-  layoutIfNeeded: (Rect) => void
   updateItemSize: (Key, Size) => void
-  updateLayoutNode: (Key, LayoutInfo, LayoutInfo) => void
   validate: (InvalidationContext<ListLayoutProps>) => void
 }

TableLayout

changed by:

  • TableLayoutOptions
  • TableLayoutProps
 TableLayout<T> {
-  addVisibleLayoutInfos: (Array<LayoutInfo>, LayoutNode, Rect) => void
-  binarySearch: (Array<LayoutNode>, Point, 'x' | 'y') => void
-  buildBody: (number) => LayoutNode
-  buildCell: (GridNode<T>, number, number) => LayoutNode
-  buildCollection: () => Array<LayoutNode>
-  buildColumn: (GridNode<T>, number, number) => LayoutNode
-  buildHeader: () => LayoutNode
-  buildHeaderRow: (GridNode<T>, number, number) => LayoutNode
-  buildNode: (GridNode<T>, number, number) => LayoutNode
-  buildPersistedIndices: () => void
-  buildRow: (GridNode<T>, number, number) => LayoutNode
   collection: TableCollection<T>
-  columnLayout: TableColumnLayout<T>
   columnWidths: Map<Key, number>
   constructor: (TableLayoutOptions<T>) => void
-  controlledColumns: Map<Key, GridNode<unknown>>
-  endResize: () => void
-  getColumnMaxWidth: (Key) => number
-  getColumnMinWidth: (Key) => number
-  getColumnWidth: (Key) => number
   getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
-  getEstimatedHeight: (GridNode<T>, number, number, number) => void
-  getRenderedColumnWidth: (GridNode<T>) => void
-  getResizerPosition: () => Key
   getVisibleLayoutInfos: (Rect) => void
   isLoading: any
   lastCollection: TableCollection<T>
   lastPersistedKeys: Set<Key>
   persistedIndices: Map<Key, Array<number>>
-  resizingColumn: Key | null
-  setChildHeights: (Array<LayoutNode>, number) => void
-  startResize: (Key) => void
+  scrollContainer: 'table' | 'body'
   stickyColumnIndices: Array<number>
-  uncontrolledColumns: Map<Key, GridNode<unknown>>
-  uncontrolledWidths: Map<Key, ColumnSize>
-  updateResizedColumns: (Key, number) => Map<Key, ColumnSize>
-  wasLoading: any
+  validate: (InvalidationContext<TableLayoutProps>) => void
 }

ListLayoutProps

-
+ListLayoutProps {
+  isLoading?: boolean
+}

it changed:

  • ListLayout

TableLayoutOptions

-
+TableLayoutOptions<T> {
+  scrollContainer?: 'table' | 'body'
+}

it changed:

  • TableLayout

TableLayoutProps

-
+TableLayoutProps {
+  columnWidths?: Map<Key, number>
+}

it changed:

  • TableLayout

@react-stately/table

TableColumnResizeState

 TableColumnResizeState<T> {
+  columnWidths: Map<Key, number>
   endResize: () => void
   getColumnMaxWidth: (Key) => number
   getColumnMinWidth: (Key) => number
   getColumnWidth: (Key) => number
   startResize: (Key) => void
   tableState: TableState<T>
   updateResizedColumns: (Key, number) => Map<Key, ColumnSize>
 }
 

it changed:

  • useTableColumnResizeState

TableCollection

 TableCollection<T> {
   _size: number
   at: (number) => void
   body: GridNode<T>
   columns: Array<GridNode<T>>
   constructor: (Iterable<GridNode<T>>, ITableCollection<T>, GridCollectionOptions) => void
+  getChildren: (Key) => Iterable<GridNode<T>>
   getFirstKey: () => void
   getItem: (Key) => void
   getKeyAfter: (Key) => void
   getKeyBefore: (Key) => void
   getLastKey: () => void
   getTextValue: (Key) => string
   headerRows: Array<GridNode<T>>
   rowHeaderColumnKeys: Set<Key>
   size: any
   undefined: () => void
 }
 

TableColumnLayout

 TableColumnLayout<T> {
   buildColumnWidths: (number, TableCollection<T>, Map<Key, ColumnSize>) => void
   columnMaxWidths: Map<Key, number>
   columnMinWidths: Map<Key, number>
   columnWidths: Map<Key, number>
   constructor: (TableColumnLayoutOptions<T>) => void
   getColumnMaxWidth: (Key) => number
   getColumnMinWidth: (Key) => number
   getColumnWidth: (Key) => number
   getDefaultMinWidth: (GridNode<T>) => ColumnSize | null | undefined
   getDefaultWidth: (GridNode<T>) => ColumnSize | null | undefined
   getInitialUncontrolledWidths: (Map<Key, GridNode<T>>) => Map<Key, ColumnSize>
   recombineColumns: (Array<GridNode<T>>, Map<Key, ColumnSize>, Map<Key, GridNode<T>>, Map<Key, GridNode<T>>) => Map<Key, ColumnSize>
-  resizeColumnWidth: (number, TableCollection<T>, Map<Key, ColumnSize>, Map<Key, ColumnSize>, any, number) => Map<Key, ColumnSize>
+  resizeColumnWidth: (TableCollection<T>, Map<Key, ColumnSize>, Key, number) => Map<Key, ColumnSize>
   splitColumnsIntoControlledAndUncontrolled: (Array<GridNode<T>>) => [Map<Key, GridNode<T>>, Map<Key, GridNode<T>>]
 }

@react-stately/virtualizer

Layout

 Layout<O = any, T extends {}> {
   getContentSize: () => Size
+  getItemRect: (Key) => Rect
   getLayoutInfo: (Key) => LayoutInfo
   getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
+  getVisibleRect: () => Rect
   shouldInvalidate: (Rect, Rect) => boolean
   updateItemSize: (Key, Size) => boolean
   validate: (InvalidationContext<O>) => void
   virtualizer: Virtualizer<{}, any, any>
 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RAC: Multi select with Shift is not working in Virtualized GridList
8 participants